// Copyright (c) 2016, Open DICOMweb Project. All rights reserved.
// Use of this source code is governed by the open source license
// that can be found in the LICENSE file.
// Author: Jim Philbin <jfphilbin@gmail.edu>
// See the AUTHORS file for other contributors.

import 'dart:convert';
import 'dart:typed_data';

import 'package:dicom_core/src/element/base/string.dart';
import 'package:dicom_core/src/element/byte_data/bd_element.dart';
import 'package:dicom_core/src/element/errors.dart';
import 'package:dicom_core/src/element/tag/tag_element_mixin.dart';
import 'package:dicom_core/src/empty_list.dart';
import 'package:dicom_core/src/tag/errors.dart';
import 'package:dicom_core/src/tag/tag.dart';
import 'package:dicom_core/src/uid/uid.dart';

class AEtag extends AE with TagElement<String> {
  @override
  final Tag tag;
  @override
  Iterable<String> values;

  factory AEtag(Tag tag, [Iterable<String> vList = kEmptyStringList]) =>
      (AE.isValidArgs(tag, vList))
          ? AEtag._(tag, vList)
          : invalidValuesError(vList, tag: tag);

  AEtag._(this.tag, this.values);

  AEtag.fromBytes(Tag tag, Uint8List bytes)
      : tag = (Tag.isValidVR(tag, AE.kVRIndex)) ? tag : invalidTagError(tag),
        values = AE.fromBytes(bytes);

  @override
  AEtag update([Iterable<String> vList = kEmptyStringList]) =>
      AEtag(tag, vList ?? kEmptyStringList);

  @override
  AEtag updateF(Iterable<String> f(Iterable<String> vList)) =>
      AEtag(tag, f(values));

  static AEtag make(Tag tag, Iterable vList) =>
      AEtag(tag, vList as Iterable<String>);

  static AEtag fromB64(Tag tag, String base64s) =>
      AEtag.fromBytes(tag, base64.decode(base64s));

  static AEtag fromBD(BDElement bd) => AEtag.fromBytes(bd.tag, bd.vfBytes);
}

class CStag extends CS with TagElement<String> {
  @override
  final Tag tag;
  @override
  Iterable<String> values;

  factory CStag(Tag tag, [Iterable<String> vList = kEmptyStringList]) =>
      (CS.isValidArgs(tag, vList))
          ? CStag._(tag, vList)
          : invalidValuesError(vList, tag: tag);

  CStag._(this.tag, this.values);

  CStag.fromBytes(Tag tag, Uint8List bytes)
      : tag = (Tag.isValidVR(tag, CS.kVRIndex)) ? tag : invalidTagError(tag),
        values = CS.fromBytes(bytes);

  @override
  CStag update([Iterable<String> vList = kEmptyStringList]) =>
      CStag(tag, vList ?? kEmptyStringList);

  @override
  CStag updateF(Iterable<String> f(Iterable<String> vList)) =>
      CStag(tag, f(values));

  static CStag make(Tag tag, Iterable vList) =>
      CStag(tag, vList as Iterable<String> );

  static CStag fromB64(Tag tag, String base64s) =>
      CStag.fromBytes(tag, base64.decode(base64s));

  static CStag fromBD(BDElement bd) => CStag.fromBytes(bd.tag, bd.vfBytes);
}

class DStag extends DS with TagElement<String> {
  @override
  final Tag tag;
  @override
  Iterable<String> values;

  factory DStag(Tag tag, [Iterable<String> vList = kEmptyStringList]) =>
      (DS.isValidArgs(tag, vList))
          ? DStag._(tag, vList)
          : invalidValuesError(vList, tag: tag);

  DStag._(this.tag, this.values);

  DStag.fromBytes(Tag tag, Uint8List bytes)
      : tag = (Tag.isValidVR(tag, DS.kVRIndex)) ? tag : invalidTagError(tag),
        values = DS.fromBytes(bytes);

  @override
  DStag update([Iterable<String> vList = kEmptyStringList]) =>
      DStag(tag, vList ?? kEmptyStringList);

  @override
  DStag updateF(Iterable<String> f(Iterable<String> vList)) =>
      DStag(tag, f(values));

  static DStag make(Tag tag, Iterable vList) =>
      DStag(tag, vList  as Iterable<String> );

  static DStag fromB64(Tag tag, String base64s) =>
      DStag.fromBytes(tag, base64.decode(base64s));

  static DStag fromBD(BDElement bd) => DStag.fromBytes(bd.tag, bd.vfBytes);
}

class IStag extends IS with TagElement<String> {
  @override
  final Tag tag;
  @override
  Iterable<String> values;

  factory IStag(Tag tag, [Iterable<String> vList = kEmptyStringList]) =>
      (IS.isValidArgs(tag, vList))
          ? IStag._(tag, vList)
          : invalidValuesError(vList, tag: tag);

  IStag._(this.tag, this.values);

  IStag.fromBytes(Tag tag, Uint8List bytes)
      : tag = (Tag.isValidVR(tag, IS.kVRIndex)) ? tag : invalidTagError(tag),
        values = IS.fromBytes(bytes);

  @override
  IStag update([Iterable<String> vList = kEmptyStringList]) =>
      IStag(tag, vList ?? kEmptyStringList);

  @override
  IStag updateF(Iterable<String> f(Iterable<String> vList)) =>
      IStag(tag, f(values));

  static IStag make(Tag tag, Iterable vList) =>
      IStag(tag, vList as Iterable<String>);

  static IStag fromB64(Tag tag, String base64s) =>
      IStag.fromBytes(tag, base64.decode(base64s));

  static IStag fromBD(BDElement bd) => IStag.fromBytes(bd.tag, bd.vfBytes);
}

/// A Long String (LO) Element
class LOtag extends LO with TagElement<String> {
  @override
  final Tag tag;
  @override
  Iterable<String> values;

  factory LOtag(Tag tag, [Iterable<String> vList = kEmptyStringList]) =>
      (LO.isValidArgs(tag, vList))
          ? LOtag.internal(tag, vList)
          : invalidValuesError(vList, tag: tag);

  LOtag.internal(this.tag, this.values);

  LOtag.fromBytes(Tag tag, Uint8List bytes)
      : tag = (Tag.isValidVR(tag, LO.kVRIndex)) ? tag : invalidTagError(tag),
        values = LO.fromBytes(bytes);

  @override
  LOtag update([Iterable<String> vList = kEmptyStringList]) =>
      LOtag(tag, vList ?? kEmptyStringList);

  @override
  LOtag updateF(Iterable<String> f(Iterable<String> vList)) =>
      LOtag(tag, f(values));

  static LOtag make(Tag tag, Iterable vList) => LOtag(tag, vList as Iterable<String>);

  static LOtag fromB64(Tag tag, String base64s) =>
      LOtag.fromBytes(tag, base64.decode(base64s));

  static LOtag fromBD(BDElement bd) => LOtag.fromBytes(bd.tag, bd.vfBytes);
}

/// An Long Text (LT) Element
class LTtag extends LT with TagElement<String> {
  @override
  final Tag tag;
  @override
  Iterable<String> values;

  factory LTtag(Tag tag, [Iterable<String> vList = kEmptyStringList]) =>
      (LT.isValidArgs(tag, vList))
          ? LTtag._(tag, vList)
          : invalidValuesError(vList, tag: tag);

  LTtag._(this.tag, this.values);

  LTtag.fromBytes(Tag tag, Uint8List bytes)
      : tag = (Tag.isValidVR(tag, LT.kVRIndex)) ? tag : invalidTagError(tag),
        values = LT.fromBytes(bytes);

  @override
  LTtag update([Iterable<String> vList = kEmptyStringList]) =>
      LTtag(tag, vList ?? kEmptyStringList);

  @override
  LTtag updateF(Iterable<String> f(Iterable<String> vList)) =>
      LTtag(tag, f(values));

  static LTtag make(Tag tag, Iterable vList) =>
      LTtag(tag, vList as Iterable<String>);

  static LTtag fromB64(Tag tag, String base64s) =>
      LTtag.fromBytes(tag, base64.decode(base64s));

  static LTtag fromBD(BDElement bd) => LTtag.fromBytes(bd.tag, bd.vfBytes);
}

/// A Person Name ([PN]) Element.
class PNtag extends PN with TagElement<String> {
  @override
  final Tag tag;
  @override
  Iterable<String> values;

  factory PNtag(Tag tag, [Iterable<String> vList = kEmptyStringList]) =>
      (PN.isValidArgs(tag, vList))
          ? PNtag._(tag, vList)
          : invalidValuesError(vList, tag: tag);

  PNtag._(this.tag, this.values);

  PNtag.fromBytes(Tag tag, Uint8List bytes)
      : tag = (Tag.isValidVR(tag, PN.kVRIndex)) ? tag : invalidTagError(tag),
        values = PN.fromBytes(bytes);

  @override
  PNtag update([Iterable<String> vList = kEmptyStringList]) =>
      PNtag(tag, vList ?? kEmptyStringList);

  @override
  PNtag updateF(Iterable<String> f(Iterable<String> vList)) =>
      PNtag(tag, f(values));

  static PNtag make(Tag tag, Iterable vList) =>
      PNtag(tag, vList as Iterable<String>);

  static PNtag fromB64(Tag tag, String base64s) =>
      PNtag.fromBytes(tag, base64.decode(base64s));

  static PNtag fromBD(BDElement bd) => PNtag.fromBytes(bd.tag, bd.vfBytes);
}

/// A Short String (SH) Element
class SHtag extends SH with TagElement<String> {
  @override
  final Tag tag;
  @override
  Iterable<String> values;

  factory SHtag(Tag tag, [Iterable<String> vList = kEmptyStringList]) =>
      (SH.isValidArgs(tag, vList))
          ? SHtag._(tag, vList)
          : invalidValuesError(vList, tag: tag);

  SHtag._(this.tag, this.values);

  SHtag.fromBytes(Tag tag, Uint8List bytes)
      : tag = (Tag.isValidVR(tag, SH.kVRIndex)) ? tag : invalidTagError(tag),
        values = SH.fromBytes(bytes);

  @override
  SHtag update([Iterable<String> vList = kEmptyStringList]) =>
      SHtag(tag, vList ?? kEmptyStringList);

  @override
  SHtag updateF(Iterable<String> f(Iterable<String> vList)) =>
      SHtag(tag, f(values));

  static SHtag make(Tag tag, Iterable vList) =>
      SHtag(tag, vList as Iterable<String>);

  static SHtag fromB64(Tag tag, String base64s) =>
      SHtag.fromBytes(tag, base64.decode(base64s));

  static SHtag fromBD(BDElement bd) => SHtag.fromBytes(bd.tag, bd.vfBytes);
}

/// An Short Text (ST) Element
class STtag extends ST with TagElement<String> {
  @override
  final Tag tag;
  @override
  Iterable<String> values;

  factory STtag(Tag tag, [Iterable<String> vList = kEmptyStringList]) =>
      (ST.isValidArgs(tag, vList))
          ? STtag._(tag, vList)
          : invalidValuesError(vList, tag: tag);

  STtag._(this.tag, this.values);

  STtag.fromBytes(Tag tag, Uint8List bytes)
      : tag = (Tag.isValidVR(tag, ST.kVRIndex)) ? tag : invalidTagError(tag),
        values = ST.fromBytes(bytes);

  @override
  STtag update([Iterable<String> vList = kEmptyStringList]) =>
      STtag(tag, vList ?? kEmptyStringList);

  @override
  STtag updateF(Iterable<String> f(Iterable<String> vList)) =>
      STtag(tag, f(values));

  static STtag make(Tag tag, Iterable vList) =>
      STtag(tag, vList as Iterable<String>);

  static STtag fromB64(Tag tag, String base64s) =>
      STtag.fromBytes(tag, base64.decode(base64s));

  static STtag fromBD(BDElement bd) => STtag.fromBytes(bd.tag, bd.vfBytes);
}

/// An Unlimited Characters (UC) Element
class UCtag extends UC with TagElement<String> {
  @override
  final Tag tag;
  @override
  Iterable<String> values;

  factory UCtag(Tag tag, [Iterable<String> vList = kEmptyStringList]) =>
      (UC.isValidArgs(tag, vList))
          ? UCtag._(tag, vList)
          : invalidValuesError(vList, tag: tag);

  UCtag._(this.tag, this.values);

  UCtag.fromBytes(Tag tag, Uint8List bytes)
      : tag = (Tag.isValidVR(tag, UC.kVRIndex)) ? tag : invalidTagError(tag),
        values = UC.fromBytes(bytes);

  @override
  UCtag update([Iterable<String> vList = kEmptyStringList]) =>
      UCtag(tag, vList ?? kEmptyStringList);

  @override
  UCtag updateF(Iterable<String> f(Iterable<String> vList)) =>
      UCtag(tag, f(values));

  static UCtag make(Tag tag, Iterable vList) =>
      UCtag(tag, vList as Iterable<String>);

  static UCtag fromB64(Tag tag, String base64s) =>
      UCtag.fromBytes(tag, base64.decode(base64s));

  static UCtag fromBD(BDElement bd) => UCtag.fromBytes(bd.tag, bd.vfBytes);
}

class UItag extends UI with TagElement<String> {
  @override
  final Tag tag;
  @override
  Iterable<String> values;

  factory UItag(Tag tag, [Iterable<String> vList = kEmptyStringList]) =>
      (UI.isValidArgs(tag, vList))
          ? UItag._(tag, vList)
          : invalidValuesError(vList, tag: tag);

  UItag._(this.tag, this.values);

  UItag.fromBytes(Tag tag, Uint8List bytes)
      : tag = (Tag.isValidVR(tag, UI.kVRIndex)) ? tag : invalidTagError(tag),
        values = UI.fromBytes(bytes);

  @override
  UItag update([Iterable<String> vList = kEmptyStringList]) =>
      UItag(tag, vList);

  @override
  UItag updateF(Iterable<String> f(Iterable<String> vList)) =>
      UItag(tag, f(values));

  static UItag make(Tag tag, Iterable vList) =>
      UItag(tag, vList as Iterable<String>);

  static UItag fromB64(Tag tag, String base64s) =>
      UItag.fromBytes(tag, base64.decode(base64s));

  static UItag fromBD(BDElement bd) => UItag.fromBytes(bd.tag, bd.vfBytes);

  static UItag parseBase64(Tag tag, String s, int vfLength) =>
      UItag.fromBytes(tag, base64.decode(s));

  static Iterable<Uid>? parse(List<String> vList) {
    final uids = <Uid>[];
    for (var i = 0; i < vList.length; i++) {
      final uid = Uid.parse(vList[i]);
      if (uid == null) return null;
    }
    return uids;
  }
}

/// Value Representation of [Uri].
///
/// The Value Multiplicity of this Element is 1.
class URtag extends UR with TagElement<String> {
  @override
  final Tag tag;
  @override
  Iterable<String> values;

  factory URtag(Tag tag, [Iterable<String> vList = kEmptyStringList]) =>
      (UR.isValidArgs(tag, vList))
          ? URtag._(tag, vList)
          : invalidValuesError(vList, tag: tag);

  URtag._(this.tag, this.values);

  URtag.fromBytes(Tag tag, Uint8List bytes)
      : tag = (Tag.isValidVR(tag, UR.kVRIndex)) ? tag : invalidTagError(tag),
        values = UR.fromBytes(bytes);

  @override
  URtag update([Iterable<String> vList = kEmptyStringList]) =>
      URtag(tag, vList ?? kEmptyStringList);

  @override
  URtag updateF(Iterable<String> f(Iterable<String> vList)) =>
      URtag(tag, f(values));

  static URtag make(Tag tag, Iterable vList) =>
      URtag(tag, vList as Iterable<String>);

  static URtag fromB64(Tag tag, String base64s) =>
      URtag.fromBytes(tag, base64.decode(base64s));

  static URtag fromBD(BDElement bd) => URtag.fromBytes(bd.tag, bd.vfBytes);
}

/// An Unlimited Text (UT) Element
class UTtag extends UT with TagElement<String> {
  @override
  final Tag tag;
  @override
  Iterable<String> values;

  factory UTtag(Tag tag, [Iterable<String> vList = kEmptyStringList]) =>
      (UT.isValidArgs(tag, vList))
          ? UTtag._(tag, vList)
          : invalidValuesError(vList, tag: tag);

  UTtag._(this.tag, this.values);

  UTtag.fromBytes(Tag tag, Uint8List bytes)
      : tag = (Tag.isValidVR(tag, UT.kVRIndex)) ? tag : invalidTagError(tag),
        values = UT.fromBytes(bytes);

  @override
  UTtag update([Iterable<String> vList = kEmptyStringList]) =>
      UTtag(tag, vList ?? kEmptyStringList);

  @override
  UTtag updateF(Iterable<String> f(Iterable<String> vList)) =>
      UTtag(tag, f(values));

  static UTtag make(Tag tag, Iterable vList) =>
      UTtag(tag, vList as Iterable<String>);

  static UTtag fromB64(Tag tag, String base64s) =>
      UTtag.fromBytes(tag, base64.decode(base64s));

  static UTtag fromBD(BDElement bd) => UTtag.fromBytes(bd.tag, bd.vfBytes);
}
